﻿/* Pierwszy program Direct X w C# */
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
/* W tym miejscu dołączamy biblioteki */
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Microsoft.DirectX.DirectInput;
using D3D = Microsoft.DirectX.Direct3D;
using MyDX;
/*Tu dochodzi kilka nowych rzeczy */

namespace DirectX_Sample
{
    public class WinForm : System.Windows.Forms.Form        // Tworzymy klasę WinForm pochodną standardowej formy z WinAPI
    {
        private D3D.Device device;  //tu pierwsza nowość. Zmienna typu Device.

        private MyObject O;
        private System.ComponentModel.Container components = null;
        private Microsoft.DirectX.DirectInput.Device keyboard;

        private MyCamera Camera;

        float time = 0.0f, time2 = 0.0f;

        private D3D.Font Font3D;

        private float fps;
        private int frames = 0, PrevTickCount = 1;

        private D3D.Effect effect;

        private Matrix mProjection, mView;


        private Vector4 LightPosition;

        public WinForm()  // konstruktor - inicjalizujemy komponenty formy (tak naprawdę to ich nie ma, ale wygodniej się pracuje w VS)
        {
            InitializeComponent();

        }


        public void InitializeKeyboard()
        {
            keyboard = new Microsoft.DirectX.DirectInput.Device(SystemGuid.Keyboard);
            keyboard.SetCooperativeLevel(this, CooperativeLevelFlags.Background | CooperativeLevelFlags.NonExclusive);
            keyboard.Acquire();
        }

        private void UpdateFramerate()
        {
            frames++;
            if (Math.Abs(Environment.TickCount - PrevTickCount) > 1000)
            {
                fps = (float)frames * 1000 / Math.Abs(Environment.TickCount - PrevTickCount);
                PrevTickCount = Environment.TickCount;
                frames = 0;
            }

        }

        //Dane swiatla
        float AmbientIntensity = 0.1f;
        float DiffuseIntensity = 1.0f;
        float SpecularIntensity = 1.0f;
        Vector4 AmbientColor = new Vector4(0.2f, 0.2f, 0.2f, 1.0f);
        Vector4 DiffuseColor = new Vector4(1f, 1f, 1f, 1.0f);
        Vector4 SpecularColor = new Vector4(1f, 1f, 1f, 1.0f);
        Texture texture;
        float SpecularPower = 5.0f;

        protected override void OnPaint(System.Windows.Forms.PaintEventArgs e) //Trzecia nowość. W zdarzeniu OnPaint będziemy rysować. 
        {
            ReadKeyboard();
            UpdateLights();
            UpdateCamera();
            UpdateFramerate();
            int passes;
            Vector3 c = Camera.Position;
            Vector4 CameraPosition = new Vector4(c.X, c.Y, c.Z, 1.0f);

            device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.Black, 1.0f, 0);
            device.RenderState.CullMode = Cull.Clockwise;
            device.BeginScene(); // początek sceny 

            effect.Technique = "lighted";
            effect.SetValue("xWorld", Matrix.Identity);
            effect.SetValue("xView", mView);
            effect.SetValue("xProjection", mProjection);
            effect.SetValue("xLightPosition", LightPosition);
            effect.SetValue("xCameraPosition", CameraPosition);
            effect.SetValue("xAmbientIntensity", AmbientIntensity);
            effect.SetValue("xAmbientColor", AmbientColor);
            effect.SetValue("xDiffuseIntensity", DiffuseIntensity);
            effect.SetValue("xDiffuseColor", DiffuseColor);
            effect.SetValue("xSpecularIntensity", SpecularIntensity);
            effect.SetValue("xSpecularColor", SpecularColor);
            effect.SetValue("xSpecularPower", SpecularPower);
            effect.SetValue("xDiffuseTexture", texture);

            device.SetStreamSource(0, O.vb, 0);
            device.VertexDeclaration = O.vd;
            effect.CommitChanges();

            passes = effect.Begin(0);
            for (int i = 0; i < passes; i++)
            {
                effect.BeginPass(i);
                O.Render(ref effect, null);
                effect.EndPass();
            }
            effect.End();

            Font3D.DrawText(null, string.Format("{0:0.00} FPS", fps), new Point(10, 10), Color.White);

            device.EndScene();  //koniec scen
            device.Present(); //tu informujemy, że Device już jest gotowe.
            this.Invalidate(); //to po to aby windows przerysował formę
        }



        public void InitializeMeshes()
        {
            O = new MyObject(ref device);

            O.LoadFromFile(@".\Resources\kulka2.x");
            O.Normalize(Matrix.RotationX((float)Math.PI / 2));
            O.NormalizeVB();
            texture = TextureLoader.FromFile(device, @".\Resources\mars.bmp");
            
            Camera = new MyCamera();
        }

        public void UpdateCamera()
        {
            Camera.Position = new Vector3(2.0f, 2.0f, 2.0f);
            Camera.Up = new Vector3(0, 0, 1.0f);
            float aspect_ratio = this.Width / (float)this.Height;
            mProjection = Matrix.PerspectiveFovLH((float)Math.PI / 4, aspect_ratio, 0.02f, O.radius * 10);  // macierz projekcji
            mView = Camera.ViewMatrix(O.center);
        }

        public void UpdateLights()
        {
            LightPosition = new Vector4(O.radius * 5.0f * (float)Math.Sin(time),  time2 ,O.radius * 5.0f * (float)Math.Cos(time), 1.0f);

        }

        public void InitializeDevice() // Druga nowość. Inicjujemy urządzenie.
        {
            PresentParameters presentParams = new PresentParameters();
            presentParams.Windowed = true;
            presentParams.SwapEffect = SwapEffect.Discard;
            presentParams.AutoDepthStencilFormat = DepthFormat.D16;
            presentParams.EnableAutoDepthStencil = true;
            device = new D3D.Device(0, D3D.DeviceType.Hardware, this, CreateFlags.HardwareVertexProcessing, presentParams);
            System.Drawing.Font Font2D = new System.Drawing.Font("Arial", 12f, FontStyle.Regular);
            Font3D = new D3D.Font(device, Font2D);


            effect = D3D.Effect.FromFile(device, @".\Resources\shadery.fx", null, ShaderFlags.None, null);
        }

        private void ReadKeyboard()
        {
            KeyboardState keys = keyboard.GetCurrentKeyboardState();

            if (keys[Key.Left])
            {
                time -= 0.05f;
            }
            if (keys[Key.Right])
            {
                time += 0.05f;
            }

            if (keys[Key.Up])
            {
                if (time2 < 15.0f)
                    time2 += 0.1f;
            }
            if (keys[Key.Down])
            {
                if(time2 > -15.0f) 
                    time2 -= 0.1f;
            }
        }

        protected override void Dispose(bool disposing) // tu zwalniamy wszystkie komponenty
        {
            if (disposing)
            {
                if (components != null)
                {
                    components.Dispose();
                }
            }
            base.Dispose(disposing);
        }

        private void InitializeComponent() // tu metoda inicjująca komponenty
        {
            this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true); //tu drugi dodatek, aby Windows dobrze przerysowywał okno.

            this.ClientSize = new System.Drawing.Size(800, 600);
            this.Name = "WinForm";
            this.Text = "simple shaders";
        }

        static void Main()  // tu uruchamiamy aplikację.
        {
            using (WinForm dx_form = new WinForm())
            {
                dx_form.InitializeKeyboard();
                dx_form.InitializeDevice(); // Czwarta nowość. Trzeba zainicjować Device
                dx_form.InitializeMeshes();
                Application.Run(dx_form);
            }
        }
    }
}
